package com.zmj.sy.mom.srv.aps.service.scheduler.resource.shift;

import cn.hutool.core.date.DatePattern;
import com.zmj.sy.mom.srv.aps.bean.entity.resource.BreakPeriod;
import com.zmj.sy.mom.srv.aps.bean.entity.resource.Shift;
import com.zmj.sy.mom.srv.aps.bean.entity.resource.ShiftType;
import com.zmj.sy.mom.srv.aps.bean.entity.resource.WeekdayRule;
import com.zmj.sy.mom.srv.aps.utils.ProjectConstants;
import lombok.Getter;
import lombok.Setter;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;

@Getter
@Setter
public class ShiftTypeDo {

    private ShiftType shiftType;

    private List<Shift> shifts;

    private List<BreakPeriod> breakPeriods;

    private List<WeekdayRule> shiftRules;

    public static ShiftTypeDo of(ShiftType shiftType, List<Shift> shifts, List<BreakPeriod> breakPeriods, List<WeekdayRule> shiftRules) {
        ShiftTypeDo shiftTypeDo = new ShiftTypeDo();
        shiftTypeDo.setShiftType(shiftType);
        shiftTypeDo.setShifts(shifts);
        shiftTypeDo.setBreakPeriods(breakPeriods);
        shiftTypeDo.setShiftRules(shiftRules);
        return shiftTypeDo;
    }

    public static <T> TimeRange timeRange(T period, LocalDate date) {
        LocalTime startTime;
        Integer offsetStartDay;
        LocalTime endTime;
        Integer offsetEndDay;
        if (period instanceof Shift) {
            startTime = ((Shift) period).getStartTime();
            offsetStartDay = ((Shift) period).getOffsetStartDay();
            endTime = ((Shift) period).getEndTime();
            offsetEndDay = ((Shift) period).getOffsetEndDay();
        } else if (period instanceof BreakPeriod) {
            startTime = ((BreakPeriod) period).getStartTime();
            offsetStartDay = ((BreakPeriod) period).getOffsetStartDay();
            endTime = ((BreakPeriod) period).getEndTime();
            offsetEndDay = ((BreakPeriod) period).getOffsetEndDay();
        } else {
            throw new IllegalArgumentException("period is not Shift or BreakPeriod");
        }
        return TimeRange.of(
                date.minusDays(offsetStartDay).atTime(startTime),
                date.plusDays(offsetEndDay).atTime(endTime)
        );
    }

    public static <T> MultiTimeRange timeRange(List<T> periods, LocalDate date) {
        if (periods == null || periods.isEmpty()) {
            return MultiTimeRange.empty();
        }
        List<TimeRange> result = new ArrayList<>();
        for (T period : periods) {
            result.add(timeRange(period, date));
        }
        return MultiTimeRange.of(result, false, false);
    }

    public List<Shift> workShifts(LocalDate date) {
        List<Shift> shifts = new ArrayList<>(this.shifts);
        if (this.shiftRules == null || this.shiftRules.isEmpty()) {
            return shifts;
        }
        for (WeekdayRule shiftRule : this.shiftRules) {
            // 时间段
            if (shiftRule.getType() == 1) {
                String value = shiftRule.getValue();
                String[] split = value.split(ProjectConstants.WEEKDAY_RULE_WEEKVALUE_SPLIT);
                LocalDate start = LocalDate.parse(split[0], DatePattern.NORM_DATE_FORMATTER);
                LocalDate end = LocalDate.parse(split[1], DatePattern.NORM_DATE_FORMATTER);
                if (Range.include(start, end, date)) {
                    shifts = shiftRule.getStatus() == 1 ? new ArrayList<>(this.shifts) : new ArrayList<>();
                }
            }
            // 班次
            if (shiftRule.getType() == 2) {
                String value = shiftRule.getValue();
                String[] split = value.split(ProjectConstants.WEEKDAY_RULE_WEEKVALUE_SPLIT);
                LocalDate shiftDate = LocalDate.parse(split[0], DatePattern.NORM_DATE_FORMATTER);
                Integer shiftId = Integer.parseInt(split[1]);
                Shift shift = this.shifts.stream().filter(s -> s.getId().equals(shiftId)).findAny().orElse(null);
                if (shift != null && shiftDate.equals(date)) {
                    if (shiftRule.getStatus() == 1) {
                        if (!shifts.contains(shift)) {
                            shifts.add(shift);
                        }
                    } else {
                        shifts.remove(shift);
                    }
                }
            }
            // 日
            if (shiftRule.getType() == 3) {
                LocalDate shiftDate = LocalDate.parse(shiftRule.getValue(), DatePattern.NORM_DATE_FORMATTER);
                if (date.equals(shiftDate)) {
                    shifts = shiftRule.getStatus() == 1 ? new ArrayList<>(this.shifts) : new ArrayList<>();
                }
            }
            // 周
            if (shiftRule.getType() == 4) {
                DayOfWeek dayOfWeek = DayOfWeek.of(Integer.parseInt(shiftRule.getValue()));
                if (dayOfWeek == date.getDayOfWeek()) {
                    shifts = shiftRule.getStatus() == 1 ? new ArrayList<>(this.shifts) : new ArrayList<>();
                }
            }
        }
        return shifts;
    }

    public MultiTimeRange timeRange(LocalDate date) {
        return timeRange(this.shifts, date).subtract(timeRange(this.breakPeriods, date));
    }

    public MultiTimeRange workTime(LocalDate date) {
        List<Shift> workShifts = workShifts(date);
        return timeRange(workShifts, date).subtract(timeRange(this.breakPeriods, date));
    }

    public MultiTimeRange workTime(Shift shift, LocalDate date) {
        MultiTimeRange subtrahend = timeRange(this.breakPeriods, date);
        return MultiTimeRange.of(timeRange(shift, date)).subtract(subtrahend);
    }

    public BigDecimal workAbility() {
        int validTime = this.shifts.stream()
                .filter(shift -> shift.getValidTime() != null)
                .mapToInt(Shift::getValidTime)
                .sum();
        int workTime = this.shifts.stream()
                .filter(shift -> shift.getTotalWorkTime() != null)
                .mapToInt(Shift::getTotalWorkTime)
                .sum();
        if (validTime == 0 || workTime == 0) {
            return BigDecimal.ONE;
        }
        BigDecimal divide = BigDecimal.valueOf(validTime).divide(BigDecimal.valueOf(workTime), 4, RoundingMode.HALF_UP);
        if (divide.compareTo(BigDecimal.ZERO) == 0) {
            return BigDecimal.ONE;
        }
        return divide;
    }

}
